package org.snia.server.ejb.dataobject;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.SequenceInputStream;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Vector;

import javax.ejb.Local;
import javax.ejb.Remote;
import javax.ejb.Stateless;

import org.snia.cdmi.ErrorCodes;
import org.snia.cdmi.auth.Principal;
import org.snia.cdmi.exception.ForbiddenException;
import org.snia.cdmi.exception.NotFoundException;
import org.snia.cdmi.model.AccessControlList;
import org.snia.cdmi.model.BaseContainer;
import org.snia.cdmi.model.ContainerStatus;
import org.snia.cdmi.model.ContainerWithAcl;
import org.snia.cdmi.model.DataObject;
import org.snia.cdmi.model.DataObjectProperties;
import org.snia.cdmi.model.DataObjectStatus;
import org.snia.cdmi.model.DataObjectVersion;
import org.snia.cdmi.model.InputDataObject;
import org.snia.cdmi.model.MetaContainer;
import org.snia.cdmi.model.MetaDataObject;
import org.snia.cdmi.model.MetaSliceData;
import org.snia.cdmi.model.Permission;
import org.snia.cdmi.util.AclUtils;
import org.snia.exception.BusinessException;
import org.snia.server.ejb.cache.CacheBean;
import org.snia.server.ejb.cache.CacheOperation;
import org.snia.server.ejb.container.ContainerBean;
import org.snia.server.ejb.dataslice.DataSliceBean;
import org.snia.server.ejb.dataslice.DataSliceOperation;
import org.snia.server.ejb.entitydata.EntityDataBean;
import org.snia.server.ejb.entitydata.EntityDataOperation;
import org.snia.server.model.cache.ContainerCache;
import org.snia.server.model.cache.DataObjectCache;
import org.snia.server.processor.MetaDataObjectXMLProcessor;
import org.snia.server.processor.MetaProcessor;
import org.snia.util.JuuidUtils;
import org.snia.util.ServiceUtils;

/**
 * UUID与URI关联，定位于对象元数据的位置 MD5 定位于用户数据的存储节点以及在存储节点上的存储位置
 * 数据片的元数据存储位置信息由MD5值决定，但与对象元数据信息在同一节点上
 * 
 * @author w90003273
 * 
 */
@Stateless
@Local(DataObjectOperation.class)
@Remote(RemoteDataObjectOperation.class)
public class DataObjectBean implements DataObjectOperation {

//	private static int STORE_MODE = 0;// 0:表示分布式API模式，1：表示Share模式，2：表示Share-Nothing模式

	// @EJB(beanName="ContainerBean") LocalContainerOperation container_local;
	CacheOperation cache_local = new CacheBean();
	ContainerBean container_local = new ContainerBean();
	DataSliceOperation slice_local = new DataSliceBean();
	EntityDataOperation data_local = new EntityDataBean();

	/**
	 * 系统将根据数据的存储类型进行存储，如果是整文件存储
	 */
	@Override
	public void createDataObject(InputDataObject object)
			throws BusinessException {

		String container = object.getContainerName();

		// 检查容器的合法性
		MetaContainer metafile = null;
		String uuid = JuuidUtils.getuuid(container);
		metafile = container_local.getMetaContainer(uuid);

		if (metafile == null) {
			throw new NotFoundException(ErrorCodes.NoSuchContainer);
		}
		if (metafile.getStatus() != ContainerStatus.Working.getValue()) {
			// 目前暂时支持只有容器状态为正常时，才允许上传数据
			throw new ForbiddenException(ErrorCodes.OperationAborted);
		}

		// 检查请求的ACL合法性
		if (!metafile.getAccessKey().equals(object.getAccessKey())) {
			// 如果访问者不是容器的Owner，需要检查对访问者进行ACL
			Map<String, String> acl = metafile.getAccessControlList();
			String permission = acl.get(object.getAccessKey());
			if (!permission.contains("W")) {// “W”表示具有写入权限
				// 拒绝请求
				throw new ForbiddenException(ErrorCodes.AccessDenied);
			}
		}

		// 获得对象的UUID
		String uri = container + "/" + object.getObjectUri();
		// 根据文件后缀获得文件类型
		// String filetype = null;
		// int typeIndex = uri.lastIndexOf(".");
		// if (typeIndex > 0) {
		// filetype = uri.substring(typeIndex - 1);
		// }

		// 获得文件的UUID
		uuid = JuuidUtils.getuuid(uri);

		// 判断版本
		// 通过UUID获得对象的元数据
		MetaDataObject objectFile = this.getMetaDataObject(uuid);
		if (objectFile != null) {
			// 对象已经存在，检查是否是第一片，如果是，将数据当作是新版本。
			if (object.getOffset() == 0
					&& objectFile.getStatus() != DataObjectStatus.Unfinished
							.getValue()) {
				// objectFile.setNewVersion(null, md5, uuid);
				objectFile.setNewVersion(uuid);// 新增新版本
			}

			// // 在当前版本进行需要追写
			// if (objectFile.getStatus() == DataObjectStatus.Unfinished
			// .getValue()) {
			// // objectFile.setNewSlice(md5, uuid);
			// }
			// if (object.isLastSlice()) {
			// // 最后一片写入成功，则修改对象的状态为可用状态
			// objectFile.setStatus(DataObjectStatus.Working.getValue());
			// }
		} else {
			objectFile = new MetaDataObject();
			objectFile.setAccessKey(object.getAccessKey());
			objectFile.setUuid(uuid);
			objectFile.setNewVersion(uuid);// 新增新版本
			// objectFile
			// .setStatus(object.isLastSlice() ? DataObjectStatus.Working
			// .getValue() : DataObjectStatus.Unfinished
			// .getValue());
			// objectFile.setCreationDate(new Date());// TODO 时间采用GMT
		}

		// 写数据到本地存储中
		String md5 = null;

		// 保存分片记录
		List<MetaSliceData> dsmfs = new ArrayList<MetaSliceData>();
		try {
			// 将数据分片进行存储

			InputStream in = object.getContent();

			int stream_length = 16 * 1024;// 每次固定从流中读取的数据长度
			int block_length = 4 * 1024 * 1024;// 一个数据块的固定大小
			byte[] buf_block = new byte[block_length + stream_length]; // 用来临时存放已读取的数据

			int bytesRead = 0;// 实际读取的数据长度
			int offest = 0; // 每次填充数据块时的偏移位置

			while ((bytesRead = in.read(buf_block, offest, stream_length)) != -1) {
				offest += bytesRead;
				// System.out.println(offest);
				// md5 = MD5Util.getMD5(buf_request);
				if (offest > block_length) {
					// swapStream.write(buf_request, 0, bytesRead);
					// 开启一个并行线程，计算MD5，写入数据
					InputStream input_slice = new ByteArrayInputStream(
							buf_block, 0, block_length);
					byte[] slice_md5Hash = ServiceUtils
							.computeMD5Hash(input_slice);
					md5 = ServiceUtils.toHex(slice_md5Hash);
					// 判断数据块是否已经存在
					MetaSliceData sliceMeta = slice_local.getMetaSliceData(md5);
					if (sliceMeta == null) {
						// 数据块不存在，则写新数据块到存储，并增加对数据块的元数据索引文件
						data_local.createRealData(md5, input_slice);// 有两种方式：散列到独立存储或本地共享存储，本地共享存储又有文件系统存储和API存储两种形式

						sliceMeta = new MetaSliceData();// 元数据存储有带外模式，带内模式。带内模式又有散列模式与拼接模式。
						sliceMeta.setMd5(md5);
						sliceMeta.addReference(1);
						sliceMeta.addVersion(objectFile.getCurrentVersionId());
					} else {
						// 数据块以及数据库索引文件存在，则只添加对数据块的引用关系
						List<String> ls_vo = sliceMeta.getVersionObject();
						if (ls_vo == null || ls_vo.size() == 0) {
							// FIXME 正常系统不会走到该流程，执行该代码表示系统有问题
							sliceMeta.addReference(1);
							sliceMeta.addVersion(objectFile
									.getCurrentVersionId());
						} else {
							// 如果上传的版本对象不在列表中，
							if (!ls_vo.contains(objectFile
									.getCurrentVersionId())) {
								sliceMeta.addReference(1);
								sliceMeta.addVersion(objectFile
										.getCurrentVersionId());
							}
						}
					}
					// 将数据块索引文件放入列表，以便后续将片刷新到对象的元数据文件中
					dsmfs.add(sliceMeta);
					// 以上并行线程结束

					// 将超出的数据对象再从头写回buf_block数据块。
					int over_length = offest - block_length;
					InputStream input_slice1 = new ByteArrayInputStream(
							buf_block, block_length, over_length);
					input_slice1.read(buf_block, 0, over_length);
					offest = over_length;
				}
			}

			// 开启一个并行线程，计算MD5，写入数据
			InputStream input_slice = new ByteArrayInputStream(buf_block, 0,
					offest);
			byte[] slice_md5Hash = ServiceUtils.computeMD5Hash(input_slice);
			md5 = ServiceUtils.toHex(slice_md5Hash);// 采用16进制表示法
			// 判断数据块是否已经存在
			MetaSliceData sliceMeta = slice_local.getMetaSliceData(md5);
			if (sliceMeta == null) {
				// 数据块不存在，则写新数据块到存储，并增加对数据块的元数据索引文件
				data_local.createRealData(md5, input_slice);// 有两种方式：散列到独立存储或本地共享存储，本地共享存储又有文件系统存储和API存储两种形式

				sliceMeta = new MetaSliceData();// 元数据存储有带外模式，带内模式。带内模式又有散列模式与拼接模式。
				sliceMeta.setMd5(md5);
				sliceMeta.addReference(1);
				sliceMeta.addVersion(objectFile.getCurrentVersionId());
			} else {
				// 数据块以及数据库索引文件存在，则只添加对数据块的引用关系
				List<String> ls_vo = sliceMeta.getVersionObject();
				if (ls_vo == null || ls_vo.size() == 0) {
					// FIXME 正常系统不会走到该流程，执行该代码表示系统有问题
					sliceMeta.addReference(1);
					sliceMeta.addVersion(objectFile.getCurrentVersionId());
				} else {
					// 如果上传的版本对象不在列表中，
					if (!ls_vo.contains(objectFile.getCurrentVersionId())) {
						sliceMeta.addReference(1);
						sliceMeta.addVersion(objectFile.getCurrentVersionId());
					}
				}
			}
			// 将数据块索引文件放入列表，以便后续将片刷新到对象的元数据文件中
			dsmfs.add(sliceMeta);
			// 以上并行线程结束
			buf_block = null;// 要求回收

			for (MetaSliceData dsmf : dsmfs) {
				// 写数据块索引文件
				slice_local.createMetaSliceData(dsmf);
				objectFile.setNewSlice(dsmf.getMd5());
			}

			// 写数据的元数据文件
			this.createMetaDataObject(objectFile);

			// TODO 将对象的元数据放入缓存

		} catch (NoSuchAlgorithmException e1) {
			e1.printStackTrace();
		} catch (IOException e1) {
			e1.printStackTrace();
		}
	}

	// /**
	// * 检查磁盘上是否具有指定对象的元数据文件
	// *
	// * @param container
	// */
	// private boolean exist(String container, String fileuuid) {
	// String uuidpath = JuuidUtils.getuuidPath(container + File.separator
	// + fileuuid);
	// String dir = Constants.DATA_STORE_URL + File.separator + uuidpath;
	// String filepath = dir + ".xml";
	// File file = new File(filepath);
	// if (file.exists()) {
	// return true;
	// }
	// return false;
	// }
	//
	// /**
	// * 向本地删除用户数据
	// */
	// private void deleteObjectData(String md5, String filetype) {
	// String uuidpath = JuuidUtils.getuuidPath(md5);
	// String dir = Constants.DATA_STORE_URL + File.separator + uuidpath;
	// String filepath = dir + File.separator + md5 + filetype;
	// // TODO 检查文件的引用次数，如果不为1，则不会删除数据，但会更改引用文件。
	// File file = new File(filepath);
	// if (file.exists()) {
	// file.delete();
	// }
	// }

	// /**
	// * 序列化元数据文件对象到磁盘
	// *
	// * @param container
	// */
	// private void toXMLFile(MetaDataObject metafile) {
	// try {
	// String uuidpath = JuuidUtils.getuuidPath(metafile.getUuid());
	// String dir = Constants.MATA_DATAOBJECT_ROOT + File.separator
	// + uuidpath;
	// File folder = new File(dir);
	// if (!folder.exists()) {
	// folder.mkdirs();
	// }
	// String filepath = dir + File.separator + metafile.getUuid()
	// + ".xml";
	// // 序列化为文件
	// SerializeUtil.serializeToXml(metafile, filepath);
	// } catch (Exception e) {
	// e.printStackTrace();
	// }
	// }

	@Override
	public void createMetaDataObject(MetaDataObject meta) {
		MetaProcessor processor = new MetaDataObjectXMLProcessor();
		processor.createMetadata(meta.getUuid(), meta);
	}

	/**
	 * 删除本节点上的完整数据对象
	 */
	@Override
	public void deleteDataObject(String container, String objectUri,
			Principal principal) {
		// 根据容器的状态判断是否可以进行删除操作，比较缓存
		BaseContainer b_container = (ContainerCache) cache_local
				.getCache(container);
		if (b_container != null
				&& (b_container.getStatus() != ContainerStatus.Working.getValue())) {
			// 只有容器状态为正常时，才能进行容器内对象的删除，否则返回409.冲突
			throw new ForbiddenException(ErrorCodes.OperationAborted);
		}
		// 根据容器的状态判断是否可以进行删除操作，比较元数据
		ContainerWithAcl acl_container = container_local.getMetaContainer(container);
		if (acl_container == null) {
			// 元数据文件不存在，说明容器不存在，容器内的对象更加不存在
			throw new NotFoundException(ErrorCodes.NoSuchObject);
		} else if (b_container.getStatus() != ContainerStatus.Working.getValue()) {
			// 只有容器状态为正常时，才能进行容器内对象的删除，否则返回409.冲突
			throw new ForbiddenException(ErrorCodes.OperationAborted);
		}

		// 检查要删除的对象是否存在,如果存在，判断ACL
		// 1.获得对象的UUID
		String uri = container + "/" + objectUri;
		String uuid = JuuidUtils.getuuid(uri);
		// 2.根据UUID检查缓存和元数据
//		BaseDataObject b_object = (DataObjectCache) cache_local.getCache(uuid);
		MetaDataObject m_object = this.getMetaDataObject(uuid);
		if (m_object == null) {
			// 说明要请求的数据不存在
			throw new NotFoundException(ErrorCodes.NoSuchObject);
		}

		// 3.判断ACL访问权限。
		if(!AclUtils.checkAcl(acl_container, m_object, Permission.Write, principal)){
			throw new ForbiddenException(ErrorCodes.AccessDenied);
		}

		// 如果容器不允许多版本或只有一个版本，按照如下顺序进行删除：对象缓存，关联关系(暂无)，
		// 对象元数据，对象块数据，对象块的父空目录
		int max_version = acl_container.getMaxMumber4Version();
		List<DataObjectVersion> all_version = m_object.getVersions();
		// TODO 开始锁要删除的文件

		// 小于2表示没有设置版本
		if (max_version > 1 && all_version.size() > 1) {
			// TODO 只删除当前版本
		} else {
			// 全部删除
			cache_local.deleteCache(uuid);
			// 为了保证一致性，先删除元数据
			this.deleteMetaDataObject(uuid);
			// TODO 放到消息服务中异步删除
			for (DataObjectVersion version : all_version) {
				List<String> slices = version.getSlices();
				// TODO 修改为并行删除
				for (String md5 : slices) {
					slice_local.deleteDataSlice(md5);
				}
			}

		}
	}

	@Override
	public DataObject getDataObject(String container, String objectUri,
			Principal principal) {
		// 根据容器的状态判断是否可以进行删除操作，比较缓存

		ContainerCache c_cache = (ContainerCache) cache_local
				.getCache(container);
		MetaContainer c_meta = null;
		if (c_cache != null
				&& (c_cache.getStatus() != ContainerStatus.Working.getValue())) {
			// 只有容器状态为正常时，才能进行容器内对象的读取，否则返回409.冲突
			throw new ForbiddenException(ErrorCodes.OperationAborted);
		}

		// 根据容器的状态判断是否可以进行读取操作，比较元数据
		String c_uuid = JuuidUtils.getuuid(container);
		c_meta = container_local.getMetaContainer(c_uuid);
		if (c_meta == null) {
			// 元数据文件不存在，说明容器不存在，容器内的对象更加不存在
			throw new NotFoundException(ErrorCodes.NoSuchObject);
		} else if (c_meta.getStatus() != ContainerStatus.Working.getValue()) {
			// 只有容器状态为正常时，才能进行容器内对象的删除，否则返回409.冲突
			throw new ForbiddenException(ErrorCodes.OperationAborted);
		}

		// 检查要删除的对象是否存在,如果存在，判断ACL
		// 1.获得对象的UUID
		String uri = container + "/" + objectUri;
		String o_uuid = JuuidUtils.getuuid(uri);
		// 2.根据UUID检查缓存和元数据
		// FIXME 可能出现Object的UUID与容器的名称相同的情况，这将导致异常
		DataObjectCache o_cache = (DataObjectCache) cache_local.getCache(o_uuid);
		MetaDataObject o_meta = this.getMetaDataObject(o_uuid);
		if (o_meta == null) {
			// 说明要请求的数据不存在
			throw new NotFoundException(ErrorCodes.NoSuchObject);
		}

		// 3.判断ACL，优先判断对象的ACL权限，但容器的拥有人可以做任何事情。
		String s_principal = principal.getIdentifier();
		if ((c_cache != null && !c_cache.getAccessKey().equals(s_principal))
				|| (!c_meta.getAccessKey().equals(s_principal))) {
			if ((o_cache != null && !o_cache.getAccessKey().equals(s_principal))
					|| !o_meta.getAccessKey().equals(s_principal)) {
				Map<String, String> acl = c_cache.getAccessControlList();
				String permission = acl.get(s_principal);
				if (!permission.contains("R")) {// “R”表示具有读取权限
					acl = o_meta.getAccessControlList();
					permission = acl.get(s_principal);
					if (!permission.contains("R")) {
						throw new ForbiddenException(ErrorCodes.AccessDenied);
					}
				}
			}
		}

		// 读取对象的当前版本
		String c_version = o_meta.getCurrentVersionId();
		List<DataObjectVersion> all_verion = o_meta.getVersions();
		DataObjectVersion c_data_verion = null;
		for (DataObjectVersion data_version : all_verion) {
			if (data_version.getVersionId().equals(c_version)) {
				c_data_verion = data_version;
				break;
			}
		}

		if (c_data_verion == null) {
			// FIXME 抛出系统异常，系统没有找到对应数据，数据可能已被破坏。
			new NotFoundException(ErrorCodes.NoSuchObject);
		}

		// 获得所有的文件块
		List<String> slices = c_data_verion.getSlices();
		// 通过片的MD5值获得片数据实体
		InputStream stream = null;
		DataObject dto = new DataObject();
		dto.setMimetype("binary/octet-stream");
		Vector<InputStream> vector = new Vector<InputStream>();
		for (String md5 : slices) {
			stream = data_local.getRealData(md5);
			vector.add(stream);
		}

		// 将多个流进行合并后输出
		SequenceInputStream minput = new SequenceInputStream(vector.elements());
		dto.setContent(minput);
		return dto;
	}

	@Override
	public void deleteMetaDataObject(String uri) {
		MetaProcessor processor = new MetaDataObjectXMLProcessor();
		processor.deleteMetadata(uri);
	}

	@Override
	public void updateMetaDataObject(String uuid, MetaDataObject meta) {
		MetaProcessor processor = new MetaDataObjectXMLProcessor();
		processor.updateMetadata(uuid, meta);
	}

	/**
	 * 集合任务
	 */
	@Override
	public void setAccessControlPolicy(String container, String objectUri,
			AccessControlList acl, Principal principal) {
		ContainerCache cache = null;
		cache = (ContainerCache) cache_local.getCache(container);
		if (cache != null) {
			if (cache.getStatus() != ContainerStatus.Working.getValue()) {
				// 只有容器状态为正常时，才能进行操作，否则返回409.冲突
				throw new ForbiddenException(ErrorCodes.OperationAborted);
			}
		}

		String c_uuid = JuuidUtils.getuuid(container);
		MetaContainer meta_container = container_local
				.getMetaContainer(c_uuid);
		// 从元数据文件，获得容器状态，进行比较
		if (meta_container != null) {
			if (meta_container.getStatus() != ContainerStatus.Working
					.getValue()) {
				// 只有容器状态为正常时，才能进行操作，否则返回409.冲突
				throw new ForbiddenException(ErrorCodes.OperationAborted);
			}
		} else {
			// 元数据文件不存在
			throw new NotFoundException(ErrorCodes.NoSuchContainer);
		}

		// 获得对象的元数据信息
		String uri = container + "/" + objectUri;
		String uuid = JuuidUtils.getuuid(uri);
		MetaDataObject meta = this.getMetaDataObject(uuid);

		// 将ACL写入元数据
		meta.setAccessControlPolicy(acl);
		this.updateMetaDataObject(uuid, meta);

	}

	/**
	 * 集合任务
	 */
	@Override
	public Map<String, String> getAccessControlList(String container,
			String objectUri, Principal principal) {
		// 1.获得对象的UUID
		String uri = container + "/" + objectUri;
		String uuid = JuuidUtils.getuuid(uri);
		Map<String, String> aclMap = null;
		MetaDataObject meta = this.getMetaDataObject(uuid);
		if (meta != null) {
			aclMap = meta.getAccessControlList();
		} else {
			throw new NotFoundException(ErrorCodes.NoSuchObject);
		}
		return aclMap;
	}

	@Override
	public DataObjectProperties getProperties(String container,
			String objectUri, boolean withMetadata, Principal principal) {
		// 由于容器的元数据信息以及基本属性并不会被缓存起来
		// 所以这里直接读元数据文件
		// 1.获得对象的UUID
		String uri = container + "/" + objectUri;
		String uuid = JuuidUtils.getuuid(uri);

		DataObjectProperties properties = null;
		MetaDataObject metafile = this.getMetaDataObject(uuid);
		if (metafile != null) {
			properties = new DataObjectProperties();
			properties.setCreationDate(metafile.getCreationDate());
			properties.setStatus(metafile.getStatus());
			// TODO 未完
			if (withMetadata) {
				properties.setUserMetadata(metafile.getUserMetadata());
			}
		} else {
			throw new NotFoundException(ErrorCodes.NoSuchObject);
		}
		return properties;
	}

	/**
	 * 集合任务
	 */
	@Override
	public void setUserMetadata(String container, String objectUri,
			Map<String, String> meta, Principal principal) {
		// 1.获得对象的UUID
		String uri = container + "/" + objectUri;
		String uuid = JuuidUtils.getuuid(uri);
		MetaDataObject metafile = this.getMetaDataObject(uuid);
		if (metafile != null) {
			metafile.setUserMetadata(meta);
			this.updateMetaDataObject(uuid, metafile);
		} else {
			throw new NotFoundException(ErrorCodes.NoSuchObject);
		}

	}

	@Override
	public Map<String, String> getUserMetadata(String container,
			String objectUri, Principal principal) {
		// 1.获得对象的UUID
		String uri = container + "/" + objectUri;
		String uuid = JuuidUtils.getuuid(uri);
		Map<String, String> meta = null;
		MetaDataObject metafile = this.getMetaDataObject(uuid);
		if (metafile != null) {
			meta = metafile.getUserMetadata();
		} else {
			throw new NotFoundException(ErrorCodes.NoSuchObject);
		}
		return meta;
	}

	@Override
	public MetaDataObject getMetaDataObject(String uuid) {
		MetaProcessor processor = new MetaDataObjectXMLProcessor();
		Object meta = processor.getMetadata(uuid);
		return (meta == null) ? null : (MetaDataObject) meta;
	}

	@Override
	public MetaDataObject copyDataObject(String source, String container,
			String objectUri, boolean replaceFlag, Principal principal) {
		return null;
		// TODO Auto-generated method stub

	}
}
